﻿using System;
using System.Runtime.CompilerServices;

namespace DimensionsHelper.Common.Metadata.Script;

/// <summary>
///     脚本中的分类值，使用花括号包裹的一系列值 ({E1, E2 .. } 或 { 1, 2, ... }) 中的单个元素。
/// </summary>
public class CategoryValue : IEquatable<CategoryValue>
{
    private UnionValue _value;

    public CategoryValue()
    {
        _value = new UnionValue(intValue: 0, stringValue: "", valueType: ValueType.Integer);
    }

    public CategoryValue(CategoryValue value)
    {
        _value = value._value;
    }

    /// <summary>
    ///     获取当前分类值，可能是字符串或者整数。
    /// </summary>
    public object Value => _value.GetValue();

    public bool Equals(CategoryValue? other)
    {
        if (other is null)
        {
            return false;
        }

        if (ReferenceEquals(this, other))
        {
            return true;
        }

        return _value == other._value;
    }

    /// <summary>
    ///     设置当前分类元素值为整数值。
    /// </summary>
    /// <param name="value"></param>
    public void SetValue(int value)
    {
        _value = new UnionValue(intValue: value, stringValue: string.Empty, valueType: ValueType.Integer);
    }

    /// <summary>
    ///     设置当前分类元素值为分类名。
    /// </summary>
    /// <param name="value"></param>
    public void SetValue(string value)
    {
        if (int.TryParse(value, out int integer))
        {
            SetValue(integer);
            return;
        }

        _value = new UnionValue(intValue: 0, stringValue: value, valueType: ValueType.Name);
    }

    /// <summary>
    ///     判断给定名称是否和当前分类元素名相同。
    /// </summary>
    /// <param name="name">给定分类名</param>
    /// <returns></returns>
    public bool Equals(string name)
    {
        return _value.ValueType == ValueType.Name &&
               _value.StringValue.Equals(name, StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    ///     判断给定整数是否和当前分类值相等。
    /// </summary>
    /// <param name="value">给定整数</param>
    /// <returns></returns>
    public bool Equals(int value)
    {
        if (_value.ValueType == ValueType.Integer)
        {
            return _value.IntValue == value;
        }

        return false;
    }

    public override bool Equals(object? obj)
    {
        if (obj is null)
        {
            return false;
        }

        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        switch (obj)
        {
            case int integer:
                return _value.ValueType == ValueType.Integer && _value.IntValue == integer;
            case string str:
                return (_value.ValueType == ValueType.Integer && int.TryParse(str, out int i) &&
                        i == _value.IntValue) ||
                       (_value.ValueType == ValueType.Name &&
                        string.Equals(_value.StringValue, str, StringComparison.OrdinalIgnoreCase));
            case CategoryValue categorical:
                return categorical._value == _value;
            default:
                return false;
        }
    }

    public override int GetHashCode()
    {
        return RuntimeHelpers.GetHashCode(this);
    }

    public static bool operator ==(CategoryValue? left, int right)
    {
        if (left?._value.ValueType == ValueType.Integer)
        {
            return left._value.IntValue == right;
        }

        return false;
    }

    public static bool operator ==(CategoryValue? left, string right)
    {
        return left?._value.ValueType == ValueType.Name &&
               left._value.StringValue.Equals(right, StringComparison.OrdinalIgnoreCase);
    }

    public static bool operator ==(CategoryValue? left, CategoryValue right)
    {
        return ReferenceEquals(left, right) ||
               left?.Equals(right) == true;
    }

    public static bool operator !=(CategoryValue left, int right)
    {
        return !(left == right);
    }

    public static bool operator !=(CategoryValue left, string right)
    {
        return !(left == right);
    }

    public static bool operator !=(CategoryValue? left, CategoryValue right)
    {
        return !ReferenceEquals(left, right) && left?.Equals(right) != true;
    }

    public static bool operator >(CategoryValue left, int right)
    {
        if (left._value.ValueType == ValueType.Integer)
        {
            return left._value.IntValue > right;
        }

        return false;
    }

    public static bool operator <(CategoryValue left, int right)
    {
        if (left._value.ValueType == ValueType.Integer)
        {
            return left._value.IntValue < right;
        }

        return false;
    }

    private readonly struct UnionValue(int intValue, string stringValue, ValueType valueType) : IEquatable<UnionValue>
    {
        public int IntValue { get; } = intValue;
        public string StringValue { get; } = stringValue;
        public ValueType ValueType { get; } = valueType;

        public object GetValue()
        {
            return ValueType == ValueType.Integer ? IntValue : StringValue;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(IntValue, StringValue, ValueType);
        }

        public bool Equals(UnionValue obj)
        {
            if (obj.ValueType != ValueType)
            {
                return false;
            }

            if (obj.ValueType == ValueType.Integer)
            {
                return IntValue == obj.IntValue;
            }

            return string.Equals(StringValue, obj.StringValue, StringComparison.OrdinalIgnoreCase);
        }

        public override bool Equals(object? obj)
        {
            return obj is UnionValue value && Equals(value);
        }

        public static bool operator ==(UnionValue left, UnionValue right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(UnionValue left, UnionValue right)
        {
            return !left.Equals(right);
        }
    }

    private enum ValueType
    {
        Integer,
        Name
    }
}